﻿//LogControl.xaml.cs
//Copyright (c) 2013 StockSharp LLC, all rights reserved.
//This code module is part of StockSharp library.
//This code is licensed under the GNU GENERAL PUBLIC LICENSE Version 3.
//See the file License.txt for the license details.
//More info on: http://stocksharp.com

namespace StockSharp.Xaml
{
	using System;
	using System.Collections.Generic;
	using System.Collections.Specialized;
	using System.ComponentModel;
	using System.Text;
	using System.Windows;
	using System.Windows.Data;
	using System.Windows.Input;
	using System.Windows.Controls;

	using Ecng.Common;
	using Ecng.Serialization;
	using Ecng.Xaml;

	using StockSharp.Logging;

	/// <summary>
	/// Графический компонент для отображения логов.
	/// </summary>
	public partial class LogControl : ILogListener, IPersistable
	{
		private bool _dirty;

		/// <summary>
		/// Создать <see cref="LogControl"/>.
		/// </summary>
		public LogControl()
		{
			InitializeComponent();

			Messages = new ThreadSafeObservableCollection<LogMessage>();
			GuiDispatcher.GlobalDispatcher.AddPeriodicalAction(UpdateOnDirty);
		}

		#region Dependency properties

		/// <summary>
		/// <see cref="DependencyProperty"/> для <see cref="AutoScroll"/>.
		/// </summary>
		public static readonly DependencyProperty AutoScrollProperty =
			DependencyProperty.Register("AutoScroll", typeof(bool), typeof(LogControl), new PropertyMetadata(false, AutoScrollChanged));

		private static void AutoScrollChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
		{
			var ctrl = d.FindLogicalChild<LogControl>();
			var autoScroll = (bool)e.NewValue;

			ctrl.AutoScrollMenuItem.IsChecked = autoScroll;
			ctrl._autoScroll = autoScroll;
		}

		private bool _autoScroll;

		/// <summary>
		/// Автоматически скролировать контрол на последнюю добавленную строку. По умолчанию false.
		/// </summary>
		public bool AutoScroll
		{
			get { return _autoScroll; }
			set { SetValue(AutoScrollProperty, value); }
		}

		/// <summary>
		/// <see cref="DependencyProperty"/> для <see cref="AutoResize"/>.
		/// </summary>
		public static readonly DependencyProperty AutoResizeProperty =
			DependencyProperty.Register("AutoResize", typeof(bool), typeof(LogControl), new PropertyMetadata(false, AutoResizeChanged));

		private static void AutoResizeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
		{
			var ctrl = d.FindLogicalChild<LogControl>();
			var autoResize = (bool)e.NewValue;

			ctrl.AutoResizeMenuItem.IsChecked = autoResize;
			ctrl._autoResize = autoResize;
		}

		private bool _autoResize;

		/// <summary>
		/// Автоматически выровнивать ширину колонок по содержимому. По умолчанию false.
		/// </summary>
		public bool AutoResize
		{
			get { return _autoResize; }
			set { SetValue(AutoResizeProperty, value); }
		}

		private const int _defaultMaxItemsCount = 10000;

		/// <summary>
		/// <see cref="DependencyProperty"/> для <see cref="MaxItemsCount"/>.
		/// </summary>
		public static readonly DependencyProperty MaxItemsCountProperty =
			DependencyProperty.Register("MaxItemsCount", typeof(int), typeof(LogControl),
				new PropertyMetadata(_defaultMaxItemsCount, MaxItemsCountChanged));

		private static void MaxItemsCountChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
		{
			var ctrl = d.FindLogicalChild<LogControl>();
			var maxCount = (int)e.NewValue;

			if (maxCount < 0)
				throw new ArgumentOutOfRangeException();

			ctrl._maxItemsCount = maxCount;
		}

		private int _maxItemsCount = _defaultMaxItemsCount;

		/// <summary>
		/// Максимальное число записей для отображения. По умолчанию отображается последние 10000 записей.
		/// Значение 0 обозначает бесконечный объем записей.
		/// </summary>
		public int MaxItemsCount
		{
			get { return _maxItemsCount; }
			set { SetValue(MaxItemsCountProperty, value); }
		}

		/// <summary>
		/// <see cref="DependencyProperty"/> для <see cref="ShowSourceNameColumn"/>.
		/// </summary>
		public static readonly DependencyProperty ShowSourceNameColumnProperty =
			DependencyProperty.Register("ShowSourceNameColumn", typeof(bool), typeof(LogControl), new PropertyMetadata(true, ShowSourceNameColumnChanged));

		private static void ShowSourceNameColumnChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
		{
			var ctrl = d.FindLogicalChild<LogControl>();
			var columns = ((GridView)ctrl.LogEntryDetails.View).Columns;
			var show = (bool)e.NewValue;

			if (show)
			{
				if (!columns.Contains(ctrl.SourceNameColumn))
					columns.Insert(0, ctrl.SourceNameColumn);
			}
			else
			{
				columns.Remove(ctrl.SourceNameColumn);
			}
		}

		/// <summary>
		/// Показывать столбец с названием источника. По-умолчанию true.
		/// </summary>
		public bool ShowSourceNameColumn
		{
			get { return (bool)GetValue(ShowSourceNameColumnProperty); }
			set { SetValue(ShowSourceNameColumnProperty, value); }
		}

		private const string _defaultTimeFormat = "yy/MM/dd\nHH:mm:ss.fff";

		/// <summary>
		/// <see cref="DependencyProperty"/> для <see cref="TimeFormat"/>.
		/// </summary>
		public static readonly DependencyProperty TimeFormatProperty =
			DependencyProperty.Register("TimeFormat", typeof(string), typeof(LogControl), new PropertyMetadata(_defaultTimeFormat, TimeFormatChanged));

		private static void TimeFormatChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
		{
			var ctrl = (LogControl)d;
			var timeFormat = (string)e.NewValue;

			if (timeFormat.IsEmpty())
				throw new ArgumentNullException();

			ctrl._timeFormat = timeFormat;
		}

		private string _timeFormat = _defaultTimeFormat;

		/// <summary>
		/// Формат конвертирования времени в строку. По-умолчанию формат равен yy/MM/dd\nHH:mm:ss.fff
		/// </summary>
		public string TimeFormat
		{
			get { return _timeFormat; }
			set { SetValue(TimeFormatProperty, value); }
		}

		/// <summary>
		/// <see cref="DependencyProperty"/> для <see cref="ShowErrors"/>.
		/// </summary>
		public static readonly DependencyProperty ShowErrorsProperty =
			DependencyProperty.Register("ShowErrors", typeof(bool), typeof(LogControl), new PropertyMetadata(true, ShowChanged));

		/// <summary>
		/// Показывать ошибки. По умолчанию true.
		/// </summary>
		public bool ShowErrors
		{
			get { return (bool)GetValue(ShowErrorsProperty); }
			set { SetValue(ShowErrorsProperty, value); }
		}

		/// <summary>
		/// <see cref="DependencyProperty"/> для <see cref="ShowWarnings"/>.
		/// </summary>
		public static readonly DependencyProperty ShowWarningsProperty =
			DependencyProperty.Register("ShowWarnings", typeof(bool), typeof(LogControl), new PropertyMetadata(true, ShowChanged));

		/// <summary>
		/// Показывать ошибки. По умолчанию true.
		/// </summary>
		public bool ShowWarnings
		{
			get { return (bool)GetValue(ShowWarningsProperty); }
			set { SetValue(ShowWarningsProperty, value); }
		}

		/// <summary>
		/// <see cref="DependencyProperty"/> для <see cref="ShowInformations"/>.
		/// </summary>
		public static readonly DependencyProperty ShowInformationsProperty =
			DependencyProperty.Register("ShowInformations", typeof(bool), typeof(LogControl), new PropertyMetadata(true, ShowChanged));

		/// <summary>
		/// Показывать ошибки. По умолчанию true.
		/// </summary>
		public bool ShowInformations
		{
			get { return (bool)GetValue(ShowInformationsProperty); }
			set { SetValue(ShowInformationsProperty, value); }
		}

		private static void ShowChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
		{
			d.FindLogicalChild<LogControl>().RefreshData();
		}

		#endregion

		#region Attached properties

		/// <summary>
		/// <see cref="DependencyProperty"/> для <see cref="AutoScroll"/>.
		/// </summary>
		public static readonly DependencyProperty LogAutoScrollProperty = 
			DependencyProperty.RegisterAttached("LogAutoScroll", typeof(bool),  typeof(LogControl), new PropertyMetadata(false, AutoScrollChanged));

		/// <summary>
		/// Установить значение для <see cref="AutoScroll"/>.
		/// </summary>
		/// <param name="element"></param>
		/// <param name="value"></param>
		public static void SetLogAutoScroll(UIElement element, bool value)
		{
			element.SetValue(LogAutoScrollProperty, value);
		}

		/// <summary>
		/// Получить значение для <see cref="AutoScroll"/>.
		/// </summary>
		/// <param name="element"></param>
		/// <returns></returns>
		public static bool GetLogAutoScroll(UIElement element)
		{
			return (bool)element.GetValue(LogAutoScrollProperty);
		}

		/// <summary>
		/// <see cref="DependencyProperty"/> для <see cref="AutoResize"/>.
		/// </summary>
		public static readonly DependencyProperty LogAutoResizeProperty =
			DependencyProperty.Register("LogAutoResize", typeof(bool), typeof(LogControl), new PropertyMetadata(false, AutoResizeChanged));

		/// <summary>
		/// Установить значение для <see cref="AutoResize"/>.
		/// </summary>
		/// <param name="element"></param>
		/// <param name="value"></param>
		public static void SetLogAutoResize(UIElement element, bool value)
		{
			element.SetValue(LogAutoResizeProperty, value);
		}

		/// <summary>
		/// Получить значение для <see cref="AutoResize"/>.
		/// </summary>
		/// <param name="element"></param>
		/// <returns></returns>
		public static bool GetLogAutoResize(UIElement element)
		{
			return (bool)element.GetValue(LogAutoResizeProperty);
		}

		/// <summary>
		/// <see cref="DependencyProperty"/> для <see cref="MaxItemsCount"/>.
		/// </summary>
		public static readonly DependencyProperty LogMaxItemsCountProperty =
			DependencyProperty.RegisterAttached("LogMaxItemsCount", typeof(int), typeof(LogControl),  new PropertyMetadata(_defaultMaxItemsCount, MaxItemsCountChanged));

		/// <summary>
		/// Установить значение для <see cref="MaxItemsCount"/>.
		/// </summary>
		/// <param name="element"></param>
		/// <param name="value"></param>
		public static void SetLogMaxItemsCount(UIElement element, int value)
		{
			element.SetValue(LogMaxItemsCountProperty, value);
		}

		/// <summary>
		/// Получить значение для <see cref="MaxItemsCount"/>.
		/// </summary>
		/// <param name="element"></param>
		/// <returns></returns>
		public static int GetLogMaxItemsCount(UIElement element)
		{
			return (int)element.GetValue(LogMaxItemsCountProperty);
		}

		/// <summary>
		/// <see cref="DependencyProperty"/> для <see cref="ShowSourceNameColumn"/>.
		/// </summary>
		public static readonly DependencyProperty LogShowSourceNameColumnProperty =
			DependencyProperty.RegisterAttached("LogShowSourceNameColumn", typeof(bool), typeof(LogControl), new PropertyMetadata(ShowSourceNameColumnChanged));

		/// <summary>
		/// Установить значение для <see cref="ShowSourceNameColumn"/>.
		/// </summary>
		/// <param name="element"></param>
		/// <param name="value"></param>
		public static void SetLogShowSourceNameColumn(UIElement element, bool value)
		{
			element.SetValue(LogShowSourceNameColumnProperty, value);
		}

		/// <summary>
		/// Получить значение для <see cref="ShowSourceNameColumn"/>.
		/// </summary>
		/// <param name="element"></param>
		/// <returns></returns>
		public static bool GetLogShowSourceNameColumn(UIElement element)
		{
			return (bool)element.GetValue(LogShowSourceNameColumnProperty);
		}

		#endregion

		private ThreadSafeObservableCollection<LogMessage> _messages;
		private ICollectionView _view;

		/// <summary>
		/// Коллекция лог-записей.
		/// </summary>
		public ThreadSafeObservableCollection<LogMessage> Messages
		{
			get { return _messages; }
			internal set
			{
				if (value == null)
					throw new ArgumentNullException();

				if (_messages != null)
					_messages.CollectionChanged -= LogControlCollectionChanged;

				_messages = value;
				_messages.CollectionChanged += LogControlCollectionChanged;

				_view = CollectionViewSource.GetDefaultView(_messages);
				_view.Filter = obj =>
				{
					var message = obj as LogMessage;
					if (message != null)
					{
						switch (message.Level)
						{
							case LogLevels.Info:
								return ShowInformations;

							case LogLevels.Warning:
								return ShowWarnings;

							case LogLevels.Error:
							case LogLevels.Debug:
								return ShowErrors;
						}
					}

					return false;
				};

				LogEntryDetails.ItemsSource = _messages;
			}
		}

		private void RefreshData()
		{
			if(_view != null)
				_view.Refresh();
		}

		private void CopyCanExecute(object sender, CanExecuteRoutedEventArgs e)
		{
			e.CanExecute = true;
			e.Handled = true;
		}

		private void CopyExecuted(object sender, ExecutedRoutedEventArgs e)
		{
			var text = new StringBuilder();

			foreach (LogMessage item in LogEntryDetails.SelectedItems)
			{
				text.AppendFormat("{0, -15} | {1} | {2, -10} | {3}",
				                  item.Source.Name,
				                  item.Time.ToString("dd.MM.yyyy HH:mm:ss.fff"),
				                  GetLogType(item.Level), item.Message);

				text.AppendLine();
			}

			text.To<string>().TryCopyToClipboard();
			e.Handled = true;
		}

		private static string GetLogType(LogLevels level)
		{
			switch (level)
			{
				case LogLevels.Debug:
				case LogLevels.Info:
					return string.Empty;
				case LogLevels.Warning:
					return "Внимание";
				case LogLevels.Error:
					return "Ошибка";
				default:
					return "" + level;
			}
		}

		/// <summary>
		/// Выровнять ширину колонок по содержимому.
		/// </summary>
		public void ResizeColumns()
		{
			var gridView = LogEntryDetails.View as GridView;

			if (gridView == null)
				return;

			foreach (var column in gridView.Columns)
			{
				if (column.Width.IsNaN())
				{
					column.Width = column.ActualWidth;
				}

				column.Width = double.NaN;
			}
		}

		private void LogControlCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
		{
			lock (this)
			{
				_dirty = true;
			}
		}

		private void UpdateOnDirty()
		{
			bool dirty;

			lock (this)
			{
				dirty = _dirty;
				_dirty = false;
			}

			if (!dirty)
				return;

			if (Messages.Count > 2 * MaxItemsCount && MaxItemsCount > 0)
				Messages.RemoveRange(0, Messages.Count - MaxItemsCount);

			if (!AutoScroll || LogEntryDetails.Items.Count <= 0 || Visibility != Visibility.Visible)
				return;

			if (AutoResize)
				ResizeColumns();

			var scroll = LogEntryDetails.FindVisualChild<ScrollViewer>();
			if (scroll != null)
				scroll.ScrollToEnd();
		}

		void ILogListener.WriteMessages(IEnumerable<LogMessage> messages)
		{
			if (messages == null)
				throw new ArgumentNullException("messages");

			Messages.AddRange(messages);
		}

		private void AutoScrollMenuItem_Click(object sender, RoutedEventArgs e)
		{
			//необходимо менять свойство, чтобы менялось состояние пункта меню
			AutoScroll = !AutoScroll;
		}

		private void AutoResizeMenuItem_Click(object sender, RoutedEventArgs e)
		{
			AutoResize = !AutoResize;
		}

		#region Implementation of IPersistable

		void IPersistable.Load(SettingsStorage storage)
		{
			AutoScroll = storage.GetValue("AutoScroll", false);
			AutoResize = storage.GetValue("AutoResize", false);
			ShowSourceNameColumn = storage.GetValue("ShowSourceNameColumn", true);
			MaxItemsCount = storage.GetValue("MaxItemsCount", _defaultMaxItemsCount);
			TimeFormat = storage.GetValue("TimeFormat", _defaultTimeFormat);
		}

		void IPersistable.Save(SettingsStorage storage)
		{
			storage.SetValue("AutoScroll", AutoScroll);
			storage.SetValue("AutoResize", AutoResize);
			storage.SetValue("ShowSourceNameColumn", ShowSourceNameColumn);
			storage.SetValue("MaxItemsCount", MaxItemsCount);
			storage.SetValue("TimeFormat", TimeFormat);
		}

		#endregion
	}
}